1 using System;
2 using System.Collections.Generic;
3 using UnityEngine;
4
5 namespace UnityStandardAssets.Water
6 {
7 [ExecuteInEditMode] // Make water live-update even when not in play mode
8 public class Water : MonoBehaviour
9 {
10 public enum WaterMode
11 {
12 Simple = 0,
13 Reflective = 1,
14 Refractive = 2,
15 };
16
17
18 public WaterMode waterMode = WaterMode.Refractive;
19 public bool disablePixelLights = true;
20 public int textureSize = 256;
21 public float clipPlaneOffset = 0.07f;
22 public LayerMask reflectLayers = -1;
23 public LayerMask refractLayers = -1;
24
25
26 private Dictionary<Camera, Camera> m_ReflectionCameras = new Dictionary<Camera, Camera>(); // Camera -> Camera table
27 private Dictionary<Camera, Camera> m_RefractionCameras = new Dictionary<Camera, Camera>(); // Camera -> Camera table
28 private RenderTexture m_ReflectionTexture;
29 private RenderTexture m_RefractionTexture;
30 private WaterMode m_HardwareWaterSupport = WaterMode.Refractive;
31 private int m_OldReflectionTextureSize;
32 private int m_OldRefractionTextureSize;
33 private static bool s_InsideWater;
34
35
36 // This is called when it's known that the object will be rendered by some
37 // camera. We render reflections / refractions and do other updates here.
38 // Because the script executes in edit mode, reflections for the scene view
39 // camera will just work!
40 public void OnWillRenderObject()
41 {
42 if (!enabled || !GetComponent<Renderer>() || !GetComponent<Renderer>().sharedMaterial ||
43 !GetComponent<Renderer>().enabled)
44 {
45 return;
46 }
47
48 Camera cam = Camera.current;
49 if (!cam)
50 {
51 return;
52 }
53
54 // Safeguard from recursive water reflections.
55 if (s_InsideWater)
56 {
57 return;
58 }
59 s_InsideWater = true;
60
61 // Actual water rendering mode depends on both the current setting AND
62 // the hardware support. There's no point in rendering refraction textures
63 // if they won't be visible in the end.
64 m_HardwareWaterSupport = FindHardwareWaterSupport();
65 WaterMode mode = GetWaterMode();
66
67 Camera reflectionCamera, refractionCamera;
68 CreateWaterObjects(cam, out reflectionCamera, out refractionCamera);
69
70 // find out the reflection plane: position and normal in world space
71 Vector3 pos = transform.position;
72 Vector3 normal = transform.up;
73
74 // Optionally disable pixel lights for reflection/refraction
75 int oldPixelLightCount = QualitySettings.pixelLightCount;
76 if (disablePixelLights)
77 {
78 QualitySettings.pixelLightCount = 0;
79 }
80
81 UpdateCameraModes(cam, reflectionCamera);
82 UpdateCameraModes(cam, refractionCamera);
83
84 // Render reflection if needed
85 if (mode >= WaterMode.Reflective)
86 {
87 // Reflect camera around reflection plane
88 float d = -Vector3.Dot(normal, pos) - clipPlaneOffset;
89 Vector4 reflectionPlane = new Vector4(normal.x, normal.y, normal.z, d);
90
91 Matrix4x4 reflection = Matrix4x4.zero;
92 CalculateReflectionMatrix(ref reflection, reflectionPlane);
93 Vector3 oldpos = cam.transform.position;
94 Vector3 newpos = reflection.MultiplyPoint(oldpos);
95 reflectionCamera.worldToCameraMatrix = cam.worldToCameraMatrix * reflection;
96
97 // Setup oblique projection matrix so that near plane is our reflection
98 // plane. This way we clip everything below/above it for free.
99 Vector4 clipPlane = CameraSpacePlane(reflectionCamera, pos, normal, 1.0f);
100 reflectionCamera.projectionMatrix = cam.CalculateObliqueMatrix(clipPlane);
101
102 // Set custom culling matrix from the current camera
103 reflectionCamera.cullingMatrix = cam.projectionMatrix * cam.worldToCameraMatrix;
104
105 reflectionCamera.cullingMask = ~(1 << 4) & reflectLayers.value; // never render water layer
106 reflectionCamera.targetTexture = m_ReflectionTexture;
107 bool oldCulling = GL.invertCulling;
108 GL.invertCulling = !oldCulling;
109 reflectionCamera.transform.position = newpos;
110 Vector3 euler = cam.transform.eulerAngles;
111 reflectionCamera.transform.eulerAngles = new Vector3(-euler.x, euler.y, euler.z);
112 reflectionCamera.Render();
113 reflectionCamera.transform.position = oldpos;
114 GL.invertCulling = oldCulling;
115 GetComponent<Renderer>().sharedMaterial.SetTexture("_ReflectionTex", m_ReflectionTexture);
116 }
117
118 // Render refraction
119 if (mode >= WaterMode.Refractive)
120 {
121 refractionCamera.worldToCameraMatrix = cam.worldToCameraMatrix;
122
123 // Setup oblique projection matrix so that near plane is our reflection
124 // plane. This way we clip everything below/above it for free.
125 Vector4 clipPlane = CameraSpacePlane(refractionCamera, pos, normal, -1.0f);
126 refractionCamera.projectionMatrix = cam.CalculateObliqueMatrix(clipPlane);
127
128 // Set custom culling matrix from the current camera
129 refractionCamera.cullingMatrix = cam.projectionMatrix * cam.worldToCameraMatrix;
130
131 refractionCamera.cullingMask = ~(1 << 4) & refractLayers.value; // never render water layer
132 refractionCamera.targetTexture = m_RefractionTexture;
133 refractionCamera.transform.position = cam.transform.position;
134 refractionCamera.transform.rotation = cam.transform.rotation;
135 refractionCamera.Render();
136 GetComponent<Renderer>().sharedMaterial.SetTexture("_RefractionTex", m_RefractionTexture);
137 }
138
139 // Restore pixel light count
140 if (disablePixelLights)
141 {
142 QualitySettings.pixelLightCount = oldPixelLightCount;
143 }
144
145 // Setup shader keywords based on water mode
146 switch (mode)
147 {
148 case WaterMode.Simple:
149 Shader.EnableKeyword("WATER_SIMPLE");
150 Shader.DisableKeyword("WATER_REFLECTIVE");
151 Shader.DisableKeyword("WATER_REFRACTIVE");
152 break;
153 case WaterMode.Reflective:
154 Shader.DisableKeyword("WATER_SIMPLE");
155 Shader.EnableKeyword("WATER_REFLECTIVE");
156 Shader.DisableKeyword("WATER_REFRACTIVE");
157 break;
158 case WaterMode.Refractive:
159 Shader.DisableKeyword("WATER_SIMPLE");
160 Shader.DisableKeyword("WATER_REFLECTIVE");
161 Shader.EnableKeyword("WATER_REFRACTIVE");
162 break;
163 }
164
165 s_InsideWater = false;
166 }
167
168
169 // Cleanup all the objects we possibly have created
170 void OnDisable()
171 {
172 if (m_ReflectionTexture)
173 {
174 DestroyImmediate(m_ReflectionTexture);
175 m_ReflectionTexture = null;
176 }
177 if (m_RefractionTexture)
178 {
179 DestroyImmediate(m_RefractionTexture);
180 m_RefractionTexture = null;
181 }
182 foreach (var kvp in m_ReflectionCameras)
183 {
184 DestroyImmediate((kvp.Value).gameObject);
185 }
186 m_ReflectionCameras.Clear();
187 foreach (var kvp in m_RefractionCameras)
188 {
189 DestroyImmediate((kvp.Value).gameObject);
190 }
191 m_RefractionCameras.Clear();
192 }
193
194
195 // This just sets up some matrices in the material; for really
196 // old cards to make water texture scroll.
197 void Update()
198 {
199 if (!GetComponent<Renderer>())
200 {
201 return;
202 }
203 Material mat = GetComponent<Renderer>().sharedMaterial;
204 if (!mat)
205 {
206 return;
207 }
208
209 Vector4 waveSpeed = mat.GetVector("WaveSpeed");
210 float waveScale = mat.GetFloat("_WaveScale");
211 Vector4 waveScale4 = new Vector4(waveScale, waveScale, waveScale * 0.4f, waveScale * 0.45f);
212
213 // Time since level load, and do intermediate calculations with doubles
214 double t = Time.timeSinceLevelLoad / 20.0;
215 Vector4 offsetClamped = new Vector4(
216 (float)Math.IEEERemainder(waveSpeed.x * waveScale4.x * t, 1.0),
217 (float)Math.IEEERemainder(waveSpeed.y * waveScale4.y * t, 1.0),
218 (float)Math.IEEERemainder(waveSpeed.z * waveScale4.z * t, 1.0),
219 (float)Math.IEEERemainder(waveSpeed.w * waveScale4.w * t, 1.0)
220 );
221
222 mat.SetVector("_WaveOffset", offsetClamped);
223 mat.SetVector("_WaveScale4", waveScale4);
224 }
225
226 void UpdateCameraModes(Camera src, Camera dest)
227 {
228 if (dest == null)
229 {
230 return;
231 }
232 // set water camera to clear the same way as current camera
233 dest.clearFlags = src.clearFlags;
234 dest.backgroundColor = src.backgroundColor;
235 if (src.clearFlags == CameraClearFlags.Skybox)
236 {
237 Skybox sky = src.GetComponent<Skybox>();
238 Skybox mysky = dest.GetComponent<Skybox>();
239 if (!sky || !sky.material)
240 {
241 mysky.enabled = false;
242 }
243 else
244 {
245 mysky.enabled = true;
246 mysky.material = sky.material;
247 }
248 }
249 // update other values to match current camera.
250 // even if we are supplying custom camera&projection matrices,
251 // some of values are used elsewhere (e.g. skybox uses far plane)
252 dest.farClipPlane = src.farClipPlane;
253 dest.nearClipPlane = src.nearClipPlane;
254 dest.orthographic = src.orthographic;
255 dest.fieldOfView = src.fieldOfView;
256 dest.aspect = src.aspect;
257 dest.orthographicSize = src.orthographicSize;
258 }
259
260
261 // On-demand create any objects we need for water
262 void CreateWaterObjects(Camera currentCamera, out Camera reflectionCamera, out Camera refractionCamera)
263 {
264 WaterMode mode = GetWaterMode();
265
266 reflectionCamera = null;
267 refractionCamera = null;
268
269 if (mode >= WaterMode.Reflective)
270 {
271 // Reflection render texture
272 if (!m_ReflectionTexture || m_OldReflectionTextureSize != textureSize)
273 {
274 if (m_ReflectionTexture)
275 {
276 DestroyImmediate(m_ReflectionTexture);
277 }
278 m_ReflectionTexture = new RenderTexture(textureSize, textureSize, 16);
279 m_ReflectionTexture.name = "__WaterReflection" + GetInstanceID();
280 m_ReflectionTexture.isPowerOfTwo = true;
281 m_ReflectionTexture.hideFlags = HideFlags.DontSave;
282 m_OldReflectionTextureSize = textureSize;
283 }
284
285 // Camera for reflection
286 m_ReflectionCameras.TryGetValue(currentCamera, out reflectionCamera);
287 if (!reflectionCamera) // catch both not-in-dictionary and in-dictionary-but-deleted-GO
288 {
289 GameObject go = new GameObject("Water Refl Camera id" + GetInstanceID() + " for " + currentCamera.GetInstanceID(), typeof(Camera), typeof(Skybox));
290 reflectionCamera = go.GetComponent<Camera>();
291 reflectionCamera.enabled = false;
292 reflectionCamera.transform.position = transform.position;
293 reflectionCamera.transform.rotation = transform.rotation;
294 reflectionCamera.gameObject.AddComponent<FlareLayer>();
295 go.hideFlags = HideFlags.HideAndDontSave;
296 m_ReflectionCameras[currentCamera] = reflectionCamera;
297 }
298 }
299
300 if (mode >= WaterMode.Refractive)
301 {
302 // Refraction render texture
303 if (!m_RefractionTexture || m_OldRefractionTextureSize != textureSize)
304 {
305 if (m_RefractionTexture)
306 {
307 DestroyImmediate(m_RefractionTexture);
308 }
309 m_RefractionTexture = new RenderTexture(textureSize, textureSize, 16);
310 m_RefractionTexture.name = "__WaterRefraction" + GetInstanceID();
311 m_RefractionTexture.isPowerOfTwo = true;
312 m_RefractionTexture.hideFlags = HideFlags.DontSave;
313 m_OldRefractionTextureSize = textureSize;
314 }
315
316 // Camera for refraction
317 m_RefractionCameras.TryGetValue(currentCamera, out refractionCamera);
318 if (!refractionCamera) // catch both not-in-dictionary and in-dictionary-but-deleted-GO
319 {
320 GameObject go =
321 new GameObject("Water Refr Camera id" + GetInstanceID() + " for " + currentCamera.GetInstanceID(),
322 typeof(Camera), typeof(Skybox));
323 refractionCamera = go.GetComponent<Camera>();
324 refractionCamera.enabled = false;
325 refractionCamera.transform.position = transform.position;
326 refractionCamera.transform.rotation = transform.rotation;
327 refractionCamera.gameObject.AddComponent<FlareLayer>();
328 go.hideFlags = HideFlags.HideAndDontSave;
329 m_RefractionCameras[currentCamera] = refractionCamera;
330 }
331 }
332 }
333
334 WaterMode GetWaterMode()
335 {
336 if (m_HardwareWaterSupport < waterMode)
337 {
338 return m_HardwareWaterSupport;
339 }
340 return waterMode;
341 }
342
343 WaterMode FindHardwareWaterSupport()
344 {
345 if (!GetComponent<Renderer>())
346 {
347 return WaterMode.Simple;
348 }
349
350 Material mat = GetComponent<Renderer>().sharedMaterial;
351 if (!mat)
352 {
353 return WaterMode.Simple;
354 }
355
356 string mode = mat.GetTag("WATERMODE", false);
357 if (mode == "Refractive")
358 {
359 return WaterMode.Refractive;
360 }
361 if (mode == "Reflective")
362 {
363 return WaterMode.Reflective;
364 }
365
366 return WaterMode.Simple;
367 }
368
369 // Given position/normal of the plane, calculates plane in camera space.
370 Vector4 CameraSpacePlane(Camera cam, Vector3 pos, Vector3 normal, float sideSign)
371 {
372 Vector3 offsetPos = pos + normal * clipPlaneOffset;
373 Matrix4x4 m = cam.worldToCameraMatrix;
374 Vector3 cpos = m.MultiplyPoint(offsetPos);
375 Vector3 cnormal = m.MultiplyVector(normal).normalized * sideSign;
376 return new Vector4(cnormal.x, cnormal.y, cnormal.z, -Vector3.Dot(cpos, cnormal));
377 }
378
379 // Calculates reflection matrix around the given plane
380 static void CalculateReflectionMatrix(ref Matrix4x4 reflectionMat, Vector4 plane)
381 {
382 reflectionMat.m00 = (1F - 2F * plane[0] * plane[0]);
383 reflectionMat.m01 = (- 2F * plane[0] * plane[1]);
384 reflectionMat.m02 = (- 2F * plane[0] * plane[2]);
385 reflectionMat.m03 = (- 2F * plane[3] * plane[0]);
386
387 reflectionMat.m10 = (- 2F * plane[1] * plane[0]);
388 reflectionMat.m11 = (1F - 2F * plane[1] * plane[1]);
389 reflectionMat.m12 = (- 2F * plane[1] * plane[2]);
390 reflectionMat.m13 = (- 2F * plane[3] * plane[1]);
391
392 reflectionMat.m20 = (- 2F * plane[2] * plane[0]);
393 reflectionMat.m21 = (- 2F * plane[2] * plane[1]);
394 reflectionMat.m22 = (1F - 2F * plane[2] * plane[2]);
395 reflectionMat.m23 = (- 2F * plane[3] * plane[2]);
396
397 reflectionMat.m30 = 0F;
398 reflectionMat.m31 = 0F;
399 reflectionMat.m32 = 0F;
400 reflectionMat.m33 = 1F;
401 }
402 }
403 }